package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.po.dto.CouponFormDTO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author zt
 * @since 2024-09-20
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;//优惠券的限定范围用户类
    private final IExchangeCodeService exchangeCodeService;//兑换码的用户类
    private final IUserCouponService userCouponService;//用户领取的优惠券表
    private final RedisTemplate redisTemplate;
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        //1.dto转po 保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        //2.判断是否限定了范围，如果没有则直接返回
        if(!dto.getSpecific()){
            return;
        }
        //3.校验范围scopes
        List<Long> scopes = dto.getScopes();
        if(CollUtils.isEmpty(scopes)){
            throw new BadRequestException("分类id不能为空");
        }
        //4.保存优惠券的限定范围 coupon_scope 批量新增
        List<CouponScope> csList = scopes.stream()
                .map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(csList);
    }

    @Override
    public PageDTO<com.tianji.promotion.domain.po.vo.CouponPageVO> queryCouponPage(com.tianji.promotion.domain.po.query.CouponQuery query) {
        //1.分页条件查询优惠券表 Coupon
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //2.封装vo返回
        List<com.tianji.promotion.domain.po.vo.CouponPageVO> voList = BeanUtils.copyList(records, com.tianji.promotion.domain.po.vo.CouponPageVO.class);
        return PageDTO.of(page,voList);
    }

    /***
     * 发放优惠券
     * @param id
     * @param dto
     */
    @Override
    public void issueCoupon(Long id, com.tianji.promotion.domain.po.dto.CouponIssueFormDTO dto) {
        //1.校验参数
        if(id==null||!id.equals(dto.getId())){
            throw new BadRequestException("非法参数");
        }
        //2.校验优惠券id是否存在
        Coupon coupon = this.getById(id);
        if(coupon==null){
            throw new BadRequestException("优惠券不存在");
        }
        //3.校验优惠券状态 只有待发放和暂停发放才能发放
        if(coupon.getStatus()!= CouponStatus.DRAFT && coupon.getStatus()!=CouponStatus.PAUSE){
            throw new BizIllegalException("只有待发放和暂停发放的优惠券才能发放");
        }
        //4.修改优惠券的领取开始时间和结束时间 使用有效开始和结束时间 天数和状态
        LocalDateTime now = LocalDateTime.now();
        //该变量代表优惠券是否可以立刻发放
        boolean isBeginIssue = dto.getIssueBeginTime()==null || !dto.getIssueBeginTime().isAfter(now);
        // 5.更新优惠券
        // 5.1.拷贝属性到PO
        Coupon tmp = BeanUtils.copyBean(dto, Coupon.class);
        /*// 5.2.更新状态
        if (isBeginIssue) {
            tmp.setStatus(CouponStatus.ISSUING);
            tmp.setIssueBeginTime(now);
        }else{
            tmp.setStatus(CouponStatus.UN_ISSUE);
        }
        // 5.3.写入数据库
        updateById(tmp);*/

        //5.如果优惠券是可立即发放，将优惠券信息(优惠券id、领券开始时间结束时间、发行数量、限领数量) 采用hash存入redis(主要用于数据的校验)
        if(isBeginIssue){//是立即发放
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;//prs:coupon:优惠券id
            redisTemplate.opsForHash().put(key,"issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
            redisTemplate.opsForHash().put(key,"issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            redisTemplate.opsForHash().put(key,"totalNum", String.valueOf(coupon.getTotalNum()));
            redisTemplate.opsForHash().put(key,"userLimit", String.valueOf(coupon.getUserLimit()));
        }
        //6.如果优惠的领取方式为指定发放且优惠券之前的状态是待发放，需要生成兑换码
        if(coupon.getObtainWay()== ObtainType.ISSUE && coupon.getStatus()==CouponStatus.DRAFT){
            //兑换码兑换的截止时间，就是优惠券领取的截至时间；该时间是从前端传过来封装到tmp中的
            coupon.setIssueEndTime(tmp.getIssueEndTime());
            exchangeCodeService.asyncGenerateExchangeCode(coupon);//异步生成兑换码
        }
    }

    /***
     * 查询正在发放中的优惠券
     * @return
     */
    @Override
    public List<CouponVO> queryIssuingCoupon() {
        //1.查询db coupon 条件：发放中 手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if(CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }
        //2.查询用户券表user_coupon 条件：当前用户，发放中的优惠券id
        //正在发放的优惠券id集合
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        //当前用户针对正在发放的优惠券的领取记录
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        //代表当前用户针对每一个券 领取的数量
        /*Map<Long, Long> issueMap = new HashMap<>();
        for (UserCoupon userCoupon : list) {
            Long num = issueMap.get(userCoupon.getCouponId());//代表当前用户对每一个优惠券领取的数量
            if(num==null){
                issueMap.put(userCoupon.getCouponId(),1L);
            }else{
                issueMap.put(userCoupon.getCouponId(),Long.valueOf(num.intValue()+1));
            }
        }*/
        //2.1统计当前用户针对每一个券的已领且未使用的数量
        Map<Long, Long> issueMap = list.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.2统计当前用户 针对每一个券的已领但未使用的数量
        Map<Long, Long> unuseMap = list.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //3.po转vo返回
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon coupon : couponList) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            //优惠券还有剩余(issue_num<total_num) 且(统计用户券表user_coupon取出当前用户已经领取的优惠券数量 < user_limit)
            Long issNum = issueMap.getOrDefault(coupon.getId(), 0L);
            boolean avaliable = coupon.getIssueNum() < coupon.getTotalNum() && issNum.intValue() < coupon.getUserLimit();
            vo.setAvailable(avaliable);//是否可以领取
            //统计用户优惠券表 取出当前用户已经领取且未使用的优惠券数量
            Boolean received = unuseMap.getOrDefault(coupon.getId(), 0L) > 0;
            vo.setReceived(received);//是否可以使用
            voList.add(vo);
        }
        return voList;
    }

}
